package dao

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type DbCrud[T schema.Tabler] struct {
	Db *gorm.DB

	Conditions []map[string]any
	PageNo     int32          //页号
	PageSize   int32          //页大小
	Limit      int32          //限制条数
	Orders     []string       //排序条件 "xxx desc"
	DataList   []*T           //插入、查询操作的输入与返回
	DataMap    map[string]any // 更新操作的输入（Datalist无法更新零值）
}

func (bo *DbCrud[T]) DBCreate(ctx context.Context) ([]*T, error) {
	if bo.Db == nil || bo.DataList == nil || len(bo.DataList) == 0 {
		return nil, errors.New(fmt.Sprintf("bo param err,table is %s", Zero[T]().TableName()))
	}
	db := bo.Db.WithContext(ctx)
	if err := db.CreateInBatches(bo.DataList, 100).Error; err != nil {
		return nil, errors.Wrapf(err, "table is %s", Zero[T]().TableName())
	}
	return bo.DataList, nil
}

func (bo *DbCrud[T]) DBUpdate(ctx context.Context) (int64, error) {
	if bo.Db == nil || len(bo.Conditions) == 0 || len(bo.DataMap) == 0 {
		return 0, errors.New(fmt.Sprintf("bo param err,table is %s", Zero[T]().TableName()))
	}
	db := bo.Db.WithContext(ctx)
	db = db.Model(new(T))
	db = addQueryCondition(db, bo)
	db = addLimit(db, bo)
	if err := db.Updates(bo.DataMap).Error; err != nil {
		return 0, errors.Wrapf(err, "table is %s", Zero[T]().TableName())
	}
	return db.RowsAffected, nil
}

func (bo *DbCrud[T]) DBRead(ctx context.Context) ([]*T, error) {
	if bo == nil || bo.Db == nil {
		return nil, errors.New(fmt.Sprintf("bo param err,table is %s", Zero[T]().TableName()))
	}
	db := bo.Db.WithContext(ctx)
	db = addQueryCondition(db, bo)
	db = addOrder(db, bo)
	db = addLimit(db, bo)
	if err := db.Find(&bo.DataList).Error; err != nil {
		return nil, errors.Wrapf(err, "table is %s", Zero[T]().TableName())
	}
	if len(bo.DataList) <= 0 {
		return nil, gorm.ErrRecordNotFound
	}
	return bo.DataList, nil
}

func (bo *DbCrud[T]) DBCount(ctx context.Context) (int64, error) {
	if bo == nil || bo.Db == nil {
		return 0, errors.New(fmt.Sprintf("bo param err,table is %s", Zero[T]().TableName()))
	}
	db := bo.Db.WithContext(ctx)
	db = db.Model(new(T))
	db = addQueryCondition(db, bo)
	var total int64
	if err := db.Count(&total).Error; err != nil {
		return 0, errors.Wrapf(err, "table is %s", Zero[T]().TableName())
	}
	return total, nil
}

func (bo *DbCrud[T]) DBDelete(ctx context.Context) error {
	if bo == nil || bo.Db == nil || len(bo.Conditions) == 0 {
		return errors.New(fmt.Sprintf("bo param err,table is %s", Zero[T]().TableName()))
	}
	db := bo.Db.WithContext(ctx)
	db = addQueryCondition(db, bo)
	db = addLimit(db, bo)
	if err := db.Delete(new(T)).Error; err != nil {
		return errors.Wrapf(err, "table is %s", Zero[T]().TableName())
	}
	return nil
}

func addQueryCondition[T schema.Tabler](db *gorm.DB, bo *DbCrud[T]) *gorm.DB {
	if bo == nil || db == nil {
		return db
	}
	orFlag := false //是否使用或运算
	for _, condition := range bo.Conditions {
		for k, v := range condition {
			if orFlag {
				db = db.Or(k, v)
				orFlag = false
			} else {
				db = db.Where(k, v)
			}
		}
		orFlag = true
	}
	return db
}

func addLimit[T schema.Tabler](db *gorm.DB, bo *DbCrud[T]) *gorm.DB {
	if bo == nil || db == nil {
		return db
	}
	if bo.PageNo > 0 && bo.PageSize > 0 {
		db = db.Offset(int((bo.PageNo - 1) * bo.PageSize))
		db = db.Limit(int(bo.PageSize))
	}
	if bo.Limit > 0 {
		db = db.Limit(int(bo.Limit))
	}
	return db
}

func addOrder[T schema.Tabler](db *gorm.DB, bo *DbCrud[T]) *gorm.DB {
	if bo == nil || db == nil {
		return db
	}
	//排序条件
	for _, order := range bo.Orders {
		db = db.Order(order)
	}
	return db
}

func Zero[T any]() (v T) {
	return
}
